#include <unistd.h>
#include <sys/ioctl.h>
#include <sys/prctl.h>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <stdint.h>
#include <errno.h>
#include <pthread.h>
#include <semaphore.h>
#include <fcntl.h>
#include <sys/time.h>

#include <imp/imp_audio.h>
#include <imp/imp_log.h>

#include "testopt.h"

#define TAG "test_ai_ref"

#define REF_AEC_SAMPLE_RATE 16000
#define REF_AEC_SAMPLE_TIME 20

#define REF_AUDIO_BUF_SIZE (REF_AEC_SAMPLE_RATE * sizeof(short) * REF_AEC_SAMPLE_TIME / 1000)
#define REF_AUDIO_RECORD_NUM 500

#define REF_AUDIO_RECORD_FILE_FOR_PLAY "ref_test_for_play.pcm"
#define REF_AUDIO_RECORD_FILE "ref_test_record.pcm"
#define REF_AUDIO_REF_FILE "ref_test_ref.pcm"

extern TEST_ARGS testArgs; /*Save input param*/
extern char fileNameBuf[NAME_BUF_SIZE];

static char outFileName[256] = "ref_test_ai_record.pcm";
static char refFileName[256] = "ref_test_ai_ref.pcm";
static char playFileName[256] = "ref_test_for_play.pcm";


static int  sampleRate = 16000;
static int  frmNum = 20;
static int  numPerFrm = 640;
static int  recordTime = 7500;

static int playTime = 0;

static int aoVol = 60;
static int aoGain = 28;
static int bufSize = 1280;

static void * audio_record_ref_thread(void *argv)
{
	int ret = -1;
	int record_num = 0;

	FILE *record_file = fopen(outFileName, "wb");
	if(record_file == NULL) {
		IMP_LOG_ERR(TAG, "fopen %s failed\n", outFileName);
		return NULL;
	}

	FILE *ref_file = fopen(refFileName, "wb");
	if(ref_file == NULL) {
		IMP_LOG_ERR(TAG, "fopen %s failed\n", refFileName);
		return NULL;
	}

	/* set public attribute of AI device. */
	int devID = 1;
	IMPAudioIOAttr attr;
	attr.samplerate = sampleRate;
	attr.bitwidth = AUDIO_BIT_WIDTH_16;
	attr.soundmode = AUDIO_SOUND_MODE_MONO;
	attr.frmNum = frmNum;
	attr.numPerFrm = numPerFrm;
	attr.chnCnt = 1;
	ret = IMP_AI_SetPubAttr(devID, &attr);
	if(ret != 0) {
		IMP_LOG_ERR(TAG, "set ai %d attr err: %d\n", devID, ret);
		return NULL;
	}

	memset(&attr, 0x0, sizeof(attr));
	ret = IMP_AI_GetPubAttr(devID, &attr);
	if(ret != 0) {
		IMP_LOG_ERR(TAG, "get ai %d attr err: %d\n", devID, ret);
		return NULL;
	}

	IMP_LOG_INFO(TAG, "Audio In GetPubAttr samplerate : %d\n", attr.samplerate);
	IMP_LOG_INFO(TAG, "Audio In GetPubAttr   bitwidth : %d\n", attr.bitwidth);
	IMP_LOG_INFO(TAG, "Audio In GetPubAttr  soundmode : %d\n", attr.soundmode);
	IMP_LOG_INFO(TAG, "Audio In GetPubAttr     frmNum : %d\n", attr.frmNum);
	IMP_LOG_INFO(TAG, "Audio In GetPubAttr  numPerFrm : %d\n", attr.numPerFrm);
	IMP_LOG_INFO(TAG, "Audio In GetPubAttr     chnCnt : %d\n", attr.chnCnt);

	/* enable AI device. */
	ret = IMP_AI_Enable(devID);
	if(ret != 0) {
		IMP_LOG_ERR(TAG, "enable ai %d err\n", devID);
		return NULL;
	}

	/* set audio channel attribute of AI device. */
	int chnID = 0;
	IMPAudioIChnParam chnParam;
	chnParam.usrFrmDepth = 20;
	ret = IMP_AI_SetChnParam(devID, chnID, &chnParam);
	if(ret != 0) {
		IMP_LOG_ERR(TAG, "set ai %d channel %d attr err: %d\n", devID, chnID, ret);
		return NULL;
	}

	memset(&chnParam, 0x0, sizeof(chnParam));
	ret = IMP_AI_GetChnParam(devID, chnID, &chnParam);
	if(ret != 0) {
		IMP_LOG_ERR(TAG, "get ai %d channel %d attr err: %d\n", devID, chnID, ret);
		return NULL;
	}

	IMP_LOG_INFO(TAG, "Audio In GetChnParam usrFrmDepth : %d\n", chnParam.usrFrmDepth);

	/* enable AI channel. */
	ret = IMP_AI_EnableChn(devID, chnID);
	if(ret != 0) {
		IMP_LOG_ERR(TAG, "Audio Record enable channel failed\n");
		return NULL;
	}

	ret = IMP_AI_EnableAecRefFrame(devID, chnID, 0, 0);
	if(ret != 0) {
		IMP_LOG_ERR(TAG, "Audio Record enable channel failed\n");
		return NULL;
	}

	/* Set audio channel volume. */
	int chnVol = 60;
	ret = IMP_AI_SetVol(devID, chnID, chnVol);
	if(ret != 0) {
		IMP_LOG_ERR(TAG, "Audio Record set volume failed\n");
		return NULL;
	}

	ret = IMP_AI_GetVol(devID, chnID, &chnVol);
	if(ret != 0) {
		IMP_LOG_ERR(TAG, "Audio Record get volume failed\n");
		return NULL;
	}
	IMP_LOG_INFO(TAG, "Audio In GetVol    vol : %d\n", chnVol);

	int aigain = 25;
	ret = IMP_AI_SetGain(devID, chnID, aigain);
	if(ret != 0) {
		IMP_LOG_ERR(TAG, "Audio Record Set Gain failed\n");
		return NULL;
	}

	ret = IMP_AI_GetGain(devID, chnID, &aigain);
	if(ret != 0) {
		IMP_LOG_ERR(TAG, "Audio Record Get Gain failed\n");
		return NULL;
	}
	IMP_LOG_INFO(TAG, "Audio In GetGain    gain : %d\n", aigain);

	while(1) {
		/* get audio record frame. */

		ret = IMP_AI_PollingFrame(devID, chnID, 1000);
		if (ret != 0 ) {
			IMP_LOG_ERR(TAG, "Audio Polling Frame Data error\n");
		}
		IMPAudioFrame frm;
		IMPAudioFrame ref;
		ret = IMP_AI_GetFrameAndRef(devID, chnID, &frm, &ref,BLOCK);
		if(ret != 0) {
			IMP_LOG_ERR(TAG, "Audio Get Frame Data error\n");
			return NULL;
		}

		/* Save the recording data to a file. */
		fwrite(frm.virAddr, 1, frm.len, record_file);
		fwrite(ref.virAddr, 1, ref.len, ref_file);

		/* release the audio record frame. */
		ret = IMP_AI_ReleaseFrame(devID, chnID, &frm);
		if(ret != 0) {
			IMP_LOG_ERR(TAG, "Audio release frame data error\n");
			return NULL;
		}

		if(++record_num >= recordTime)
			break;
	}

	ret = IMP_AI_DisableAecRefFrame(devID, chnID, 0, 0);
	if(ret != 0) {
		IMP_LOG_ERR(TAG, "IMP_AI_DisableAecRefFrame\n");
		return NULL;
	}

	ret = IMP_AI_DisableChn(devID, chnID);
	if(ret != 0) {
		IMP_LOG_ERR(TAG, "Audio channel disable error\n");
		return NULL;
	}

	/* disable the audio devices. */
	ret = IMP_AI_Disable(devID);
	if(ret != 0) {
		IMP_LOG_ERR(TAG, "Audio device disable error\n");
		return NULL;
	}

	fclose(record_file);
	fclose(ref_file);
	pthread_exit(0);
}

static void *audio_play_thread(void *argv)
{
	int play_time = 0;
	unsigned char *buf = NULL;
	int size = 0;
	int ret = -1;


	buf = (unsigned char *)malloc(bufSize);
	if(buf == NULL) {
		IMP_LOG_ERR(TAG, "[ERROR] %s: malloc audio buf error\n", __func__);
		return NULL;
	}

	FILE *play_file = fopen(playFileName, "rb");
	if(play_file == NULL) {
		IMP_LOG_ERR(TAG, "[ERROR] %s: fopen %s failed\n", __func__, playFileName);
		return NULL;
	}

	/* Step 1: set public attribute of AO device. */
	int devID = 0;
	IMPAudioIOAttr attr;
	attr.samplerate = sampleRate;
	attr.bitwidth = AUDIO_BIT_WIDTH_16;
	attr.soundmode = AUDIO_SOUND_MODE_MONO;
	attr.frmNum = frmNum;
	attr.numPerFrm = numPerFrm;
	attr.chnCnt = 1;
	ret = IMP_AO_SetPubAttr(devID, &attr);
	if(ret != 0) {
		IMP_LOG_ERR(TAG, "set ao %d attr err: %d\n", devID, ret);
		return NULL;
	}

	memset(&attr, 0x0, sizeof(attr));
	ret = IMP_AO_GetPubAttr(devID, &attr);
	if(ret != 0) {
		IMP_LOG_ERR(TAG, "get ao %d attr err: %d\n", devID, ret);
		return NULL;
	}

	IMP_LOG_INFO(TAG, "Audio Out GetPubAttr samplerate:%d\n", attr.samplerate);
	IMP_LOG_INFO(TAG, "Audio Out GetPubAttr   bitwidth:%d\n", attr.bitwidth);
	IMP_LOG_INFO(TAG, "Audio Out GetPubAttr  soundmode:%d\n", attr.soundmode);
	IMP_LOG_INFO(TAG, "Audio Out GetPubAttr     frmNum:%d\n", attr.frmNum);
	IMP_LOG_INFO(TAG, "Audio Out GetPubAttr  numPerFrm:%d\n", attr.numPerFrm);
	IMP_LOG_INFO(TAG, "Audio Out GetPubAttr     chnCnt:%d\n", attr.chnCnt);

	/* Step 2: enable AO device. */
	ret = IMP_AO_Enable(devID);
	if(ret != 0) {
		IMP_LOG_ERR(TAG, "enable ao %d err\n", devID);
		return NULL;
	}

	/* Step 3: enable AI channel. */
	int chnID = 0;
	ret = IMP_AO_EnableChn(devID, chnID);
	if(ret != 0) {
		IMP_LOG_ERR(TAG, "Audio play enable channel failed\n");
		return NULL;
	}

	/* Step 4: Set audio channel volume. */
	int chnVol = aoVol;
	ret = IMP_AO_SetVol(devID, chnID, chnVol);
	if(ret != 0) {
		IMP_LOG_ERR(TAG, "Audio Play set volume failed\n");
		return NULL;
	}

	ret = IMP_AO_GetVol(devID, chnID, &chnVol);
	if(ret != 0) {
		IMP_LOG_ERR(TAG, "Audio Play get volume failed\n");
		return NULL;
	}
	IMP_LOG_INFO(TAG, "Audio Out GetVol    vol:%d\n", chnVol);

	int aogain = aoGain;
	ret = IMP_AO_SetGain(devID, chnID, aogain);
	if(ret != 0) {
		IMP_LOG_ERR(TAG, "Audio Record Set Gain failed\n");
		return NULL;
	}

	ret = IMP_AO_GetGain(devID, chnID, &aogain);
	if(ret != 0) {
		IMP_LOG_ERR(TAG, "Audio Record Get Gain failed\n");
		return NULL;
	}
	IMP_LOG_INFO(TAG, "Audio Out GetGain    gain : %d\n", aogain);

	while(1) {
		size = fread(buf, 1,bufSize, play_file);
		if(size < bufSize)
			break;

		/* Step 5: send frame data. */
		IMPAudioFrame frm;
		frm.virAddr = (uint32_t *)buf;
		frm.len = size;
		ret = IMP_AO_SendFrame(devID, chnID, &frm, BLOCK);
		if(ret != 0) {
			IMP_LOG_ERR(TAG, "send Frame Data error\n");
			return NULL;
		}

		IMPAudioOChnState play_status;
		ret = IMP_AO_QueryChnStat(devID, chnID, &play_status);
		if(ret != 0) {
			IMP_LOG_ERR(TAG, "IMP_AO_QueryChnStat error\n");
			return NULL;
		}

		IMP_LOG_INFO(TAG, "Play: TotalNum %d, FreeNum %d, BusyNum %d\n",
				play_status.chnTotalNum, play_status.chnFreeNum, play_status.chnBusyNum);
		if(++play_time >= playTime){
			printf("[INFO]:Pause to play for 5s.\n");
			sleep(5);
			play_time = 0;
			printf("[INFO]:Continue to play.\n");
		}
	}

	sleep(3);
	/* Step 6: disable the audio channel. */
	ret = IMP_AO_DisableChn(devID, chnID);
	if(ret != 0) {
		IMP_LOG_ERR(TAG, "Audio channel disable error\n");
		return NULL;
	}

	/* Step 7: disable the audio devices. */
	ret = IMP_AO_Disable(devID);
	if(ret != 0) {
		IMP_LOG_ERR(TAG, "Audio device disable error\n");
		return NULL;
	}

	fclose(play_file);
	free(buf);
	pthread_exit(0);
}

static void set_aiRefTest_param(TEST_ARGS *pTestArgs)
{
	if(NULL == pTestArgs) {
		printf("fun:%s,pTestArgs is empty.\n",__func__);
		return;
	}

	if(strcmp(pTestArgs->playFileName,"") != 0)
		strncpy(playFileName,pTestArgs->playFileName,MAX_FILE_NAME_LEN);

	if(strcmp(pTestArgs->outFileName,"") != 0)
		strncpy(outFileName,pTestArgs->outFileName,MAX_FILE_NAME_LEN);

	if(strcmp(pTestArgs->refFileName,"") != 0)
		strncpy(refFileName,pTestArgs->refFileName,MAX_FILE_NAME_LEN);

	if(pTestArgs->sampleRate > 0)
		sampleRate = pTestArgs->sampleRate;

	if(pTestArgs->frmNum > 0)
		frmNum = pTestArgs->frmNum;

	if(pTestArgs->numPerFrm > 0)
		numPerFrm = pTestArgs->numPerFrm;
	else
		numPerFrm = (sampleRate/100)*4;

	bufSize = numPerFrm*sizeof(short);

	if(pTestArgs->num > 30)
		recordTime = (pTestArgs->num)*sampleRate/numPerFrm;
	else
		recordTime = 5*60*sampleRate/numPerFrm;

	playTime = 30*sampleRate*sizeof(short)/bufSize;

	if(pTestArgs->vol != 60)
		aoVol = pTestArgs->vol;

	if(pTestArgs->gain != 28)
		aoGain = pTestArgs->gain;

}

int  main(int argc, char *argv[])
{
	int ret = -1;

	ret = parse_param(argc,argv);
	if(1 == ret) {
		return 0;
	}
	if(ret < 0) {
		printf("parse_param failed.\n");
		return -1;
	} else {
		printf("parse_param Ok.\n");
	}

	set_aiRefTest_param(&testArgs);

	printf("[INFO]:outFileName = %s\n",outFileName);
	printf("[INFO]:refFileName = %s\n",refFileName);
	printf("[INFO]:sampleRate = %d\n",sampleRate);
	printf("[INFO]:frmNum = %d\n",frmNum);
	printf("[INFO]:numPerFrm = %d\n",numPerFrm);
	printf("[INFO]:aoVol = %d\n",aoVol);
	printf("[INFO]:aoGain = %d\n",aoGain);
	printf("[INFO]:bufSize = %d\n",bufSize);
	printf("[INFO:REF test record time is %ds\n",recordTime*numPerFrm/sampleRate);

	printf("[INFO]:Start ai ref test.\n");
	printf("[INFO]:Please input any key to continue.\n");
	getchar();

	pthread_t tid_ai_ref,tid_play;

	printf("[INFO] Start audio record ref test.\n");

	ret = pthread_create(&tid_ai_ref,NULL,audio_record_ref_thread,NULL);
	if(ret != 0) {
		printf("Create IMP_Audio_Record_Ref_Thread failed.\n");
		return -1;
	}
	printf("[INFO]:Start to play for ref test.\n");
	ret = pthread_create(&tid_play, NULL,audio_play_thread,NULL);
	if(ret != 0) {
		printf("Create IMP_Audio_Play_Thread failed\n");
		return -1;
	}

	pthread_join(tid_play,NULL);
	pthread_join(tid_ai_ref,NULL);

	printf("[INFO]:Finished ai ref test\n");

	return 0;
}
